package com.junxonline.instant.admin.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.junxonline.instant.common.util.common.RedisUtils;
import com.junxonline.instant.dao.entity.model.log.LogOperation;
import com.junxonline.instant.common.annotation.OperationLog;
import com.junxonline.instant.common.constant.CommonConstant;
import com.junxonline.instant.common.constant.RedisConstant;
import com.junxonline.instant.common.constant.RedisMqChannelConstant;
import com.junxonline.instant.common.constant.UserInfoConstant;
import com.junxonline.instant.common.entity.common.CommonResult;
import com.junxonline.instant.common.util.admin.TokenUtils;
import com.junxonline.instant.util.NetworkUtils;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 操作日志切面类
 *
 * @author JunX
 * @date 2021-06-16
 */
@Log4j2
@Aspect
@Component
public class OperationLogAspect {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private TokenUtils tokenUtils;

    private final ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 定义切入点为标识了注解@OperationLog的方法
     */
    @Pointcut(value = "@annotation(operation)")
    public void operationLog(OperationLog operation) {

    }

    /**
     * 前置通知
     *
     * @param joinPoint 切入点
     * @param operation 操作日志注解
     */
    @Before(value = "operationLog(operation)")
    public void before(JoinPoint joinPoint, OperationLog operation) {
        this.startTime.set(System.currentTimeMillis());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("Authorization");

        if (!StrUtil.isBlank(token)) {
            String username = tokenUtils.getUsername(token);
            if (!StrUtil.isBlank(username)) {
                // 刷新最后访问时间
                redisUtils.hset(RedisConstant.KEY_ONLINE_USER + username, UserInfoConstant.KEY_UPDATE_TIME, DateUtil.current());
            }
        }

        log.info("************************ 收到请求 ************************");
        log.info("请求地址：" + request.getRequestURI());
        log.info("请求类型：" + request.getMethod());
        log.info("操作名称：" + operation.label());
        log.info("操作类型：" + operation.type().getLabel());
        log.info("请求主机IP：" + NetworkUtils.getIpAddress(request));
        log.info("请求方法路径：" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("请求参数：" + JSON.toJSONString(joinPoint.getArgs()));
        log.info("*********************************************************");
    }

    /**
     * 后置通知
     *
     * @param joinPoint 切入点
     * @param operation 操作日志注解
     * @param result    公共响应对象
     */
    @AfterReturning(value = "operationLog(operation)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, OperationLog operation, CommonResult result) {
        this.saveLog(joinPoint, operation, CommonConstant.SUCCESS);
        Long endTime = System.currentTimeMillis();
        log.info("************************ 返回数据 ************************");
        log.info("返回响应码：" + result.getCode());
        log.info("返回响应信息：" + result.getMsg());
        log.info("返回数据：" + JSON.toJSONString(result.getData()));
        log.info("请求耗时：" + (endTime - this.startTime.get()) + "ms");
        log.info("*********************************************************");
    }

    /**
     * 异常通知
     *
     * @param joinPoint 切入点
     * @param operation 操作日志注解
     * @param e         异常
     */
    @AfterThrowing(value = "operationLog(operation)", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, OperationLog operation, Exception e) {
        this.saveLog(joinPoint, operation, CommonConstant.FAIL);
        log.error("************************ 抛出异常 ************************");
        log.error("异常名称：" + e.getClass());
        log.error("异常信息：" + e.getMessage());
        log.error("*********************************************************");
    }

    /**
     * 发送操作日志消息
     *
     * @param joinPoint 切入点
     * @param operation 操作日志注解
     * @param status    操作结果
     */
    private void saveLog(JoinPoint joinPoint, OperationLog operation, Integer status) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("Authorization");
        if (!StrUtil.isBlank(token)) {
            Long userId = tokenUtils.getUserId(token);
            if (userId != null) {
                // Redis消息队列发送操作日志消息
                LogOperation logOperation = new LogOperation();
                logOperation.setName(operation.label());
                logOperation.setType(operation.type().getValue());
                logOperation.setUrl(request.getRequestURI());
                logOperation.setCreateId(userId);
                logOperation.setUserIp(NetworkUtils.getIpAddress(request));
                logOperation.setTargetIp(request.getHeader("Host"));
                UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
                logOperation.setBrowser(ua.getBrowser().toString());
                logOperation.setOs(ua.getOs().toString());
                logOperation.setStatus(status);
                logOperation.setOperationTime(DateUtil.date());
                if (operation.isSaveParams()) {
                    String params = JSON.toJSONString(joinPoint.getArgs());
                    // 截取参数防止参数过长入库失败
                    logOperation.setParams(StrUtil.sub(params, 0, 2000));
                }
                redisUtils.convertAndSend(RedisMqChannelConstant.KEY_REDIS_MSG_CHANNEL_LOG_OPERATION, JSON.toJSONString(logOperation));
            }
        }
    }

}
